package ru.rchervot.sanasar.client.rpc;

import java.util.LinkedHashMap;
import java.util.Map;

import ru.rchervot.sanasar.client.event.EventPageChange;
import ru.rchervot.sanasar.client.event.EventQueryChange;
import ru.rchervot.sanasar.client.event.EventRPCStatus;
import ru.rchervot.sanasar.client.event.EventResultsItem;
import ru.rchervot.sanasar.client.event.EventSettingsChange;
import ru.rchervot.sanasar.client.event.HandlerSettingsChange;
import ru.rchervot.sanasar.client.model.ItemObject;
import ru.rchervot.sanasar.client.model.SearchResultsItem;
import ru.rchervot.sanasar.client.model.UserSettings;

import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.user.client.rpc.AsyncCallback;

public class CachedBatchingService implements RPCServiceAsync {
	
	private final HandlerManager eventBus;
	private final RPCServiceAsync realService;	
	private int pageSize;
	private final int CACHE_SIZE = 50;
	
	private final Cache<Action, Response> 
	cache = new Cache<Action, Response>(
			CACHE_SIZE);
	
	public CachedBatchingService(HandlerManager eventBus, 
			RPCServiceAsync realService) {
		this.eventBus = eventBus;
		this.realService = realService;
		
		listenForSettingsChange();
	}
	
	private void listenForSettingsChange() {
		eventBus.addHandler(EventSettingsChange.getType(),
				new HandlerSettingsChange() {
			@Override
			public void onSettingsChange(EventSettingsChange event) {
				applyNewSettings(event.getSettings());
			}
		});
	}
	
	private void applyNewSettings(UserSettings settings) {
		pageSize = settings.getPageSize();
	}
	
	@Override
	public <T extends GetPageResponse<SearchResultsItem>> 
	void execute(final GetPage action, final AsyncCallback<T> callback) {
		eventBus.fireEvent(EventRPCStatus.build(EventRPCStatus.Status.LOADING));

		action.setPageSize(pageSize);

		if (cache.containsKey(action)) {
			((T)cache.get(action)).getPage().setOffset(action.getOffset()); // update page in cache to current offset
			handleSuccessPage((T)cache.get(action), callback);
			System.out.println("hit page");
		} else {
			realService.execute(action, new AsyncCallback<T>() {
				@Override
				public void onFailure(Throwable caught) {
					handleFailure(callback, caught);
				}

				@Override
				public void onSuccess(T result) {
					cache.put(action, result);
					handleSuccessPage(result, callback);
				};
			});
		}
	}
	
	@Override
	public <T extends GetItemResponse<ItemObject>> void execute(
			final GetItem action, final AsyncCallback<T> callback) {
		eventBus.fireEvent(EventRPCStatus.build(EventRPCStatus.Status.LOADING));
		
		System.out.println("action.getQueryId() " + action.getQueryId());
		
		if (cache.containsKey(action)) {
			handleSuccessItem(action, (T)cache.get(action), callback);
			System.out.println("hit item");
		} else {
			realService.execute(action, new AsyncCallback<T>() {
				@Override
				public void onFailure(Throwable caught) {
					handleFailure(callback, caught);
				}
				
				@Override
				public void onSuccess(T result) {
					System.out.println("result.getDetails().isEmpty() " + result.getDetails().isEmpty());
					cache.put(action, result);
					handleSuccessItem(action, result, callback);
				};
			});
		}
	}
	
	@Override
	public <T extends GetQueryResponse> 
	void execute(final GetQuery action, final AsyncCallback<T> callback) {
		eventBus.fireEvent(EventRPCStatus.build(EventRPCStatus.Status.LOADING));
				
		if (cache.containsKey(action)) {
			handleSuccessQuery((T)cache.get(action), callback);
			System.out.println("hit query");
		} else {
			realService.execute(action, new AsyncCallback<T>() {
				@Override
				public void onFailure(Throwable caught) {
					handleFailure(callback, caught);
				}
				
				@Override
				public void onSuccess(T result) {
					cache.put(action, result);
					handleSuccessQuery(result, callback);
				};
			});
		}
	}
	
	private <T extends GetPageResponse<SearchResultsItem>>
	void handleSuccessPage(T result, AsyncCallback<T> callback) {
		eventBus.fireEvent(EventRPCStatus.build(EventRPCStatus.Status.SUCCESS));
		
		callback.onSuccess(result);

		eventBus.fireEvent(EventPageChange.build(result.getPage()));
		
		execute(new GetItem(result.getPage().getQueryID(), 
				result.getPage().getOffset()),
				new GotItem<ItemObject>() {
			public void got(ItemObject item) {}
		});
	}
	
	
	private <T extends GetItemResponse<ItemObject>> 
	void handleSuccessItem(GetItem action, T result, AsyncCallback<T> callback) {
		eventBus.fireEvent(EventRPCStatus.build(EventRPCStatus.Status.SUCCESS));
		
		callback.onSuccess(result);
		
		eventBus.fireEvent(
				new EventResultsItem<ItemObject>(result.getDetails(), 
						action.getQueryId(), action.getOffset()));
	}
	
	private <T extends GetQueryResponse>
	void handleSuccessQuery(T result, AsyncCallback<T> callback) {
		eventBus.fireEvent(EventRPCStatus.build(EventRPCStatus.Status.SUCCESS));
		
		callback.onSuccess(result);

		eventBus.fireEvent(EventQueryChange.build(result.getQuery()));
	}
	
	private void handleFailure(AsyncCallback<?> callback, Throwable caught) {
		eventBus.fireEvent(EventRPCStatus.build(EventRPCStatus.Status.FAILURE));
		
		callback.onFailure(caught);
		
		caught.printStackTrace();
	}
	

//	@Override
//	public void execute(Action action,
//			AsyncCallback<T> callback) {
//		// TODO Auto-generated method stub
//		
//	}

	
	private class Cache<K,V> {
		private final Map<K,V> m;

		public Cache(final int size) {
			m = new LinkedHashMap<K,V>(size, .75f, true) {

				private static final long serialVersionUID = 1L;

				protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
					return size() > size;
				}
			};
		}

		public void put(K arg0, V arg1) {
			m.put(arg0, arg1);
		}

		public boolean containsKey(K arg0) {
			return m.containsKey(arg0);
		}

		public V get(K arg0) {
			return m.get(arg0);
		}
	}


	@Override
	public <T extends SaveQueryResponse> void execute(final SaveQuery action,
			final AsyncCallback<T> callback) {
		eventBus.fireEvent(EventRPCStatus.build(EventRPCStatus.Status.LOADING));

		realService.execute(action, new AsyncCallback<T>() {
			@Override
			public void onFailure(Throwable caught) {
				handleFailure(callback, caught);
			}

			@Override
			public void onSuccess(T result) {
				eventBus.fireEvent(EventRPCStatus.build(EventRPCStatus.Status.SUCCESS));

				callback.onSuccess(result);
			};
		});
	}

	
	
}
